package com.ruoyi.common.utils.bean;

import org.springframework.cglib.core.Converter;
import org.springframework.util.Assert;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

/**
 * Bean
 *
 * @author tqfeiyang
 * @since 2024/11/28 11:34
 */
public class BeanCopier
{
    /**
     * 使用  WeakHashMap 防止  oom
     */
    private static final ConcurrentMap<String, org.springframework.cglib.beans.BeanCopier> beanCopierMap = new ConcurrentHashMap<>();

    private static <S , T> org.springframework.cglib.beans.BeanCopier getBeanCopier(Class<S> source , Class<T> target) {
        String beanCopierKey = generateBeanKey(source , target);
        if (!beanCopierMap.containsKey(beanCopierKey)) {
            org.springframework.cglib.beans.BeanCopier beanCopier = org.springframework.cglib.beans.BeanCopier.create(source, target, false);
            beanCopierMap.putIfAbsent(beanCopierKey, beanCopier);
        }
        return beanCopierMap.get(beanCopierKey);
    }

    public static <S , T> T copy(S source , Class<T> target) {
        Assert.notNull(source, "Source bean must be not null.");
        Assert.notNull(target, "Target bean must be not null.");
        T ret;
        try {
            ret = target.newInstance();
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException("create class[" + target.getName()
                    + "] instance error", e);
        }
        getBeanCopier(source.getClass(), target).copy(source , ret , null);
        return ret;
    }

    public static <S, T> List<T> copy(List<S> sources , Class<T> target) {
        Assert.notNull(sources, "Source bean must be not null.");
        Assert.notNull(target, "Target bean must be not null.");
        return sources.stream().map(src -> copy(src, target)).collect(Collectors.toList());
    }

    private static class DeepCopyConverter implements Converter
    {
        /**
         * The Target.
         */
        private Class<?> target;

        /**
         * Instantiates a new Deep copy converter.
         *
         * @param target
         *            the target
         */
        public DeepCopyConverter(Class<?> target) {
            this.target = target;
        }

        @Override
        public Object convert(Object value, Class targetClazz, Object methodName) {
            if (value instanceof List) {
                List values = (List) value;
                ArrayList retList =  new ArrayList(values.size());
                for (final Object source : values) {
                    String tempFieldName = methodName.toString().replace("set",
                            "");
                    String fieldName = tempFieldName.substring(0, 1)
                            .toLowerCase() + tempFieldName.substring(1);
                    Class clazz = ClassUtils.getElementType(target, fieldName);
                    retList.add(BeanCopier.copy(source, clazz));
                }
                return retList;
            } else if (value instanceof Map) {
                // TODO 暂时用不到，后续有需要再补充
            } else if (!ClassUtils.isPrimitive(targetClazz)) {
                return BeanCopier.copy(value, targetClazz);
            }
            return value;
        }
    }

    private static final String SPLICING_OPERATOR = "@";

    private static <S , T> String generateBeanKey(Class<S> source , Class<T> target) {
        Assert.notNull(source, "Source bean must be not null.");
        Assert.notNull(target, "Target bean must be not null.");
        return source.getName() + SPLICING_OPERATOR + target.getName();
    }

//    public static BeanMap toMap(Object bean) {
//        return BeanMap.create(bean);
//    }
//
//    public static <T> T fillBean(Map map, T bean) {
//        BeanMap.create(bean).putAll(map);
//        return bean;
//    }

    private static class ClassUtils {

        private static final Map<Class<?>, Class<?>> primitiveMap = new HashMap<>(16);

        static {
            primitiveMap.put(String.class, String.class);
            primitiveMap.put(Boolean.class, boolean.class);
            primitiveMap.put(Byte.class, byte.class);
            primitiveMap.put(Character.class, char.class);
            primitiveMap.put(Double.class, double.class);
            primitiveMap.put(Float.class, float.class);
            primitiveMap.put(Integer.class, int.class);
            primitiveMap.put(Long.class, long.class);
            primitiveMap.put(Short.class, short.class);
            primitiveMap.put(Date.class, Date.class);
        }

        /**
         * @description 判断基本类型
         * @see     java.lang.String#TYPE
         * @see     java.lang.Boolean#TYPE
         * @see     java.lang.Character#TYPE
         * @see     java.lang.Byte#TYPE
         * @see     java.lang.Short#TYPE
         * @see     java.lang.Integer#TYPE
         * @see     java.lang.Long#TYPE
         * @see     java.lang.Float#TYPE
         * @see     java.lang.Double#TYPE
         * @see     java.lang.Boolean#TYPE
         * @see     char#TYPE
         * @see     byte#TYPE
         * @see     short#TYPE
         * @see     int#TYPE
         * @see     long#TYPE
         * @see     float#TYPE
         * @see     double#TYPE
         * @param clazz
         * @return boolean
         */
        public static boolean isPrimitive(Class<?> clazz) {
            if (primitiveMap.containsKey(clazz)) {
                return true;
            }
            return clazz.isPrimitive();
        }

        /**
         * @description 获取方法返回值类型
         * @param tartget
         * @param fieldName
         * @return
         * @return Class<?>
         */
        public static Class<?> getElementType(Class<?> tartget, String fieldName) {
            Class<?> elementTypeClass = null;
            try {
                Type type = tartget.getDeclaredField(fieldName).getGenericType();
                ParameterizedType t = (ParameterizedType) type;
                String classStr = t.getActualTypeArguments()[0].toString().replace("class ", "");
                elementTypeClass = Thread.currentThread().getContextClassLoader().loadClass(classStr);
            } catch (ClassNotFoundException | NoSuchFieldException | SecurityException e) {
                throw new RuntimeException("get fieldName[" + fieldName + "] error", e);
            }
            return elementTypeClass;
        }

    }
}
